﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PServiceBus.Core.Interface;
using PServiceBus.Redis;
using PServiceBus.Core.Subscription;
using PServiceBus.Core.Runtime;
using PServiceBus.Core.Runtime.Transports;
using PServiceBus.Core.Provider;
using PServiceBus.Core.Runtime.Topics;
using System.Text.RegularExpressions;

namespace PServiceBus.SubscriptionManagers {
    public abstract class BaseSubscriptionManager : ISubscriptionManager {

        private static Regex _topicPatternRegex = new Regex(@"(\*\.)|(\.\*)|(\*)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
        private static readonly object _lockObject = new object();

        private IObjectProvider<ISubscriber> _provider;
        public IObjectProvider<ISubscriber> Provider {
            get {
                return _provider ?? (_provider = GetProvider());
            }
        }

        protected abstract IObjectProvider<ISubscriber> GetProvider();

        private T Return<T>(Func<IObjectProvider<ISubscriber>, T> func) {
            var provider = Provider;
            return func(provider);
        }

        private void Call(Action<IObjectProvider<ISubscriber>> action) {
            lock (_lockObject) {
                var provider = Provider;
                action(provider);
            }
        }

        #region ISubscriptionManager Members

        public string Address { get; set; }

        public void Init() {
            
        }

        public ISubscriber AddSubscriber(ISubscriber subscriber) {
            return Return(provider =>
            {
                var exists = provider.Any(s => s.Name == subscriber.Name || s.ID == subscriber.ID);
                if (exists) throw new Exception(String.Format("Subscriber[{0}] already exists", subscriber.Name));
                var sub = new Subscriber(Guid.NewGuid(), subscriber.Name) { Durable = subscriber.Durable };
                provider.Add(sub);
                subscriber.ID = sub.ID;
                return subscriber;
            });
        }

        public void UpdateSubscriber(ISubscriber subscriber) {
            Call(provider =>
            {
                var sub = provider.Get(s => s.Name == subscriber.Name);
                if (sub == null) throw new Exception(String.Format("Subscriber[{0}] does not exists", subscriber.Name));
                sub.Name = subscriber.Name;
                sub.Durable = subscriber.Durable;
                provider.Refresh(sub);
                UpdateSubscription(sub);
            });
        }

        public void DeleteSubscriber(string name) {
            Call(provider =>
            {
                var sub = provider.Get(s => s.Name == name);
                if (sub == null) throw new Exception(String.Format("Specified subscriber[{0}] does not exist", name));
                foreach (var transportInfo in sub.Transports) {
                    var transport = transportInfo.Transport as IDeletableTransport;
                    if (transport != null)
                        MethodHelper.Try(transport.Delete);
                }
                provider.Delete(s => s.Name == name);
                UpdateSubscription(sub, update: false);
            });
        }

        private void UpdateSubscription(ISubscriber subscriber, bool update = true) {
            var subscription = SubscriptionCollection.Instance;
            foreach (var topic in subscriber.Subscriptions) {
                var sub = update ? subscriber : null;
                //Register topic as wild card to include in subscribers list for every message
                if (topic.IsWildCard) {
                    subscription.AddWildCardTopic(topic.ID);
                }
                subscription.UpdateSubscriber(topic.ID, subscriber.ID, sub);
            }
            subscription.Save();
        }

        public void DeleteTransport(string subscriber, string transportName) {
            Call(provider =>
            {
                var sub = provider.Get(s => s.Name == subscriber);
                if (sub == null) throw new Exception(String.Format("Specified subscriber[{0}] does not exist", subscriber));
                var transExist = sub.Transports.Any(t => t.Name == transportName);
                if (!transExist) throw new Exception(String.Format("Specified transport[{0}] does not exists", transportName));
                sub.Transports.Remove(sub.Transports.FirstOrDefault(t => t.Name == transportName));
                provider.Refresh(sub);
                UpdateSubscription(sub);
            });
        }

        public void AddTransport(string subscriber, string transportName, ITransport transport, Guid topicID) {
            Call(provider =>
            {
                var sub = provider.Get(s => s.Name == subscriber);
                if (sub == null) throw new Exception(String.Format("Specified subscriber[{0}] does not exist", subscriber));
                var transExist = sub.Transports.Any(t => t.Name == transportName);
                if (transExist) throw new Exception(String.Format("Transport[{0}] already exists for subscriber[{1}]", transportName, subscriber));
                sub.Transports.Add(new TransportInfo() {
                    TopicID = topicID,
                    Name = transportName,
                    Transport = transport
                });
                provider.Refresh(sub);
                UpdateSubscription(sub);
            });
        }

        public void UnSubscribeFrom(string subscriber, Guid topicID, string topicName) {
            Call(provider =>
            {
                var sub = provider.Get(s => s.Name == subscriber);
                if (sub == null) throw new Exception(String.Format("Specified subscriber[{0}] does not exist", subscriber));
                var hasSubscription = sub.Subscriptions.Any(s => s.ID == topicID);
                if (!hasSubscription) 
                    throw new Exception(String.Format("Subscriber[{0}] is not subscribed to the specified topic[{1}]", subscriber, topicName));
                sub.Subscriptions.Remove(sub.Subscriptions.FirstOrDefault(s => s.ID == topicID));
                provider.Refresh(sub);
                UpdateSubscription(sub);
            });
        }

        public void SubscribeTo(string subscriber, Guid topicID, string topicName, string filter, bool needHeader, bool caseSensitive) {
            Call(provider => {
                var sub = provider.Get(s => s.Name == subscriber);
                if (sub == null) throw new Exception(String.Format("Specified subscriber[{0}] does not exist", subscriber));
                var hasSubscription = sub.Subscriptions.Any(s => s.ID == topicID);
                if (!hasSubscription) {
                    sub.Subscriptions.Add(new Core.Runtime.Topics.SubscriptionEntry()
                    {
                        ID = topicID,
                        MessageFilter = filter,
                        NeedMessageHeaders = needHeader,
                        CaseSensitive = caseSensitive,
                        IsWildCard = _topicPatternRegex.IsMatch(topicName),
                        TopicName = topicName
                    });
                    provider.Refresh(sub);
                    UpdateSubscription(sub);
                }
                else throw new Exception(String.Format("You[{0}] are already a subscriber to the topic[{1}]", subscriber, topicName));
            });
        }

        public void UpdateSubscriber(string subscriber, string topicName, string filter, bool needHeader, bool caseSensitive) {
            Call(provider =>
            {
                var sub = provider.Get(s => s.Name == subscriber);
                if (sub == null) throw new Exception(String.Format("Specified subscriber[{0}] does not exist", subscriber));
                var entry = sub.Subscriptions
                    .Select((s, i) => new { Index = i, Subscription = s })
                    .Where(x => x.Subscription.TopicName == topicName).FirstOrDefault();

                if (entry != null) {
                    var subscription = sub.Subscriptions[entry.Index];
                    subscription.MessageFilter = filter ?? subscription.MessageFilter;
                    subscription.NeedMessageHeaders = needHeader;
                    subscription.CaseSensitive = caseSensitive;
                    sub.Subscriptions[entry.Index] = subscription;
                    provider.Refresh(sub);
                    UpdateSubscription(sub);
                }
            });
        }

        public void RegisterTopic(ITopic topic) {
           
        }

        #endregion

        #region IDisposable Members

        public void Dispose() {
            _provider.Dispose();
        }

        #endregion
    }
}
